package x86;

import util.List;
import java.util.*;

import temp.Temp;
import tree.*;
import assem.*;

public class Codegen{
	
	int wordsize;
	temp.Temp ebp;
	temp.Temp eax;
	temp.Temp edx;
	temp.Temp esp;
	temp.Temp RV;
	List<Temp> cds;
	
    public Codegen(Frame f){
    	wordsize = f.wordsize();
    	ebp = Frame.ebp;
    	eax = Frame.eax;
    	edx = Frame.edx;
    	esp = Frame.esp;
    	RV = f.RV();
    	cds = f.calleeDefs();
    }
    
int count = 0;

    public List<Instr> codegen(List<Stm> body){
    	List<Instr> outPut = new List<Instr>();
    	Iterator<Stm> iter = body.iterator();
    	System.out.println("body size - " + body.size());
    	while(iter.hasNext()){
    		Stm rootNode = (Stm) iter.next();
    		processStm(outPut, rootNode);
    		
    		System.out.println("Nó " + count + ": " + rootNode.getClass()+"\n-----------------------------------------");
    		count++;
    		
    	}
        return outPut;
    }
    
    
    /*
     * Metodo que processa um no do tipo STM.
     * Verificamos a classe do no (LABEL, MOVE, etc) e tratamos caso a caso.
     * Nada e retornado neste caso, pois o no do tipo STM nao deve retornar nada, apenas ter o seu conteudo processado.
     */
    public void processStm(List<Instr> instructions, Stm rootNode){
    	
    	/*
    	 *  Caso onde a stm seja LABEL.
    	 *  Geramos uma instrução do tipo LABEL, e a colocamos no final da lista
    	 *  de instrucoes. Como esta instrucao nunca tem filhos, nada mais e feito. 
    	 */
    	if(rootNode.getClass().equals(tree.LABEL.class)){
    		tree.LABEL labelTree = (tree.LABEL)rootNode;
    		assem.LABEL labelInstr = new assem.LABEL(labelTree.getLabel().toString()+":", labelTree.getLabel());
    		instructions.append(labelInstr);
    	}
    	/*
    	 *  Caso onde a stm seja MOVE.
    	 *  Caso o nó 'Destination' seja um MEM, tratamos como uma operação de 'store'.
    	 *  Caso contrário, geramos uma instrução do tipo MOVE, e chamamos processExp tanto para a fonte quanto para o destino 
    	 *  (retornando o valor de cada uma em seus respectivos registradores). Feito isso, a colocamos no final da lista de instrucoes. 
    	 */
    	if(rootNode.getClass().equals(tree.MOVE.class)){
    		tree.MOVE moveTree = (tree.MOVE)rootNode;
    		if(moveTree.getDestination().getClass().equals(tree.MEM.class)){
    			assem.OPER storeInstr = new assem.OPER("mov [`d0], `u0", new List<temp.Temp>(processExp(instructions,((tree.MEM) moveTree.getDestination()).getExpression())), new List<temp.Temp>(processExp(instructions, moveTree.getSource())));
    			instructions.append(storeInstr);
    		}
    		else{
    			assem.MOVE moveInstr = new assem.MOVE(processExp(instructions, moveTree.getDestination()), processExp(instructions, moveTree.getSource()));
        		instructions.append(moveInstr);
    		}
    	}
    	/*
    	 *  Caso onde a stm seja EXPSTM.
    	 *  Como nenhuma instrucao deve ser adicionada a lista de instrucoes neste
    	 *  caso, simplesmente chamamos processExp para a instrucao abaixo do no. 
    	 */
    	if(rootNode.getClass().equals(tree.EXPSTM.class)){
    		tree.EXPSTM expstmTree = (tree.EXPSTM)rootNode;
    		temp.Temp rNull = processExp(instructions, expstmTree.getExpression());
    	}
    	/*
    	 *  Caso onde a stm seja SEQ.
    	 *  Como nenhuma instrucao deve ser adicionada a lista de instrucoes neste
    	 *  caso, simplesmente chamamos processStr, primeiro para a Str da esquerda,
    	 *  depois para a da direita. 
    	 */
    	if(rootNode.getClass().equals(tree.SEQ.class)){
    		tree.SEQ seqTree = (tree.SEQ)rootNode;
    		processStm(instructions, seqTree.getLeft());
    		processStm(instructions, seqTree.getRight());
    	}
    	/*
    	 *  Caso onde a stm seja JUMP.
    	 *  Verifica-se se o jump é feito apenas para um NAME, ou se é necessário calcular a expressão
    	 */
    	if(rootNode.getClass().equals(tree.JUMP.class)){
    		assem.OPER jumpInstr;
    		tree.JUMP jumpTree = (tree.JUMP)rootNode;
    		/*
    		 * Caso a expressao seja um NAME, apenas chama a instrucao com getTargets(),
    		 * pois o unico target da lista sera o NAME.
    		 */
    		if(jumpTree.getExpression().getClass().equals(tree.NAME.class)){
    			jumpInstr = new assem.OPER("jmp `j0", jumpTree.getTargets());
    		} else {
    			/*
    			 * Caso a expressao nao seja um NAME, calcula a expressao, e usa
    			 * o valor retornado na instrucao de jump.
    			 */
    			temp.Temp rJump = processExp(instructions, jumpTree.getExpression());
    			jumpInstr = new assem.OPER("jmp `u0", null, new List<temp.Temp>(rJump));
    		}
    		instructions.append(jumpInstr);
    	}
    	/*
    	 *  Caso onde a stm seja CJUMP.
    	 *  5 argumentos:
    	 *  - operacao (equal, notequal, greater than, etc)
    	 *  - operando da direita
    	 *  - operando da esquerda
    	 *  - label pra pular caso seja verdadeiro
    	 *  - label pra pular caso seja falso
    	 *  O processamento foi feito da seguinte maneira:
    	 *  - calcula os dois operandos, e guarda num registrador.
    	 *  - imprime "cmp op1 op2"
    	 *  - dependendo da operacao, imprime
    	 *  JE 	Jump if Equal				EQ (0)
			JNE Jump if Not Equal			NE (1)
			JL 	Jump if Less				LT (2)
			JLE Jump if Less or Equal		LE (3)
			JG 	Jump if Greater				GT (4)
			JGE Jump if Greater or Equal	GE (5)
			JB 	Jump if Below				ULT(6)
			JBE Jump if Below or Equal		ULE(7)
			JA 	Jump if Above				UGT(8)
			JAE Jump if Above or Equal		UGE(9)
			Mais o Label 'temp', embaixo imprime 'jmp label false', 'labeltemp', e 'jmp label true'
			O total de impressoes no arquivo final ficaria, entao:
			cmp r1 r2
			je  LABELTEMP
			jmp LABEL_FALSE
			LABELTEMP:
			jmp LABEL_TRUE
    	 */
    	if(rootNode.getClass().equals(tree.CJUMP.class)){
    		// Seta o label temporario LABELTEMP
    		temp.Label labelTemp = new temp.Label();
    		assem.LABEL labelAssemTemp = new assem.LABEL(labelTemp.toString()+":", labelTemp);
    		// Captura todos os atributos do cjump (tipo de operacao, operadores, labels, etc)
    		int operation;
    		tree.CJUMP cjumpTree = (tree.CJUMP)rootNode;
    		operation = cjumpTree.getOperation();
    		Exp leftOperator = cjumpTree.getLeft();
    		Exp rightOperator = cjumpTree.getRight();
    		temp.Label labelTrue = cjumpTree.getLabelTrue();
    		temp.Label labelFalse = cjumpTree.getLabelFalse();
    		
    		temp.Temp r1Cjump = processExp(instructions, leftOperator);
    		temp.Temp r2Cjump = processExp(instructions, rightOperator);
    		    		    		
    		// Imprime "cmp r1 r2"
    		assem.OPER cmpOper = new assem.OPER("cmp `u0, `u1", null, new List<temp.Temp>(r1Cjump, r2Cjump));
    		instructions.append(cmpOper);
    		// Imprime "cjump LABELTEMP", baseado no argumento 'operacao' recebido
    		assem.OPER cjumpInstr;
    		switch(operation){
    		case 0:
    			cjumpInstr = new assem.OPER("je `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 1:
    			cjumpInstr = new assem.OPER("jne `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 2:
    			cjumpInstr = new assem.OPER("jl `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 3:
    			cjumpInstr = new assem.OPER("jle `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 4:
    			cjumpInstr = new assem.OPER("jg `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 5:
    			cjumpInstr = new assem.OPER("jge `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 6:
    			cjumpInstr = new assem.OPER("jb `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 7:
    			cjumpInstr = new assem.OPER("jbe `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 8:
    			cjumpInstr = new assem.OPER("ja `j0", new List<temp.Label>(labelTemp));
    			break;
    		case 9:
    			cjumpInstr = new assem.OPER("jae `j0", new List<temp.Label>(labelTemp));
    			break;
    		// caso base onde o int 'op' nao foi definido (teoricamente, nunca ocorrera)
    		default:
    			cjumpInstr = new assem.OPER("jmp `j0", new List<temp.Label>(labelTemp));
    		}
    		instructions.append(cjumpInstr);
    		// Imprime jmp LABEL_FALSE
    		assem.OPER jmpLabelFalse = new assem.OPER("jmp `j0", new List<temp.Label>(labelFalse));
    		instructions.append(jmpLabelFalse);
    		// Imprime LABELTEMP:
    		instructions.append(labelAssemTemp);
    		// Imprime jmp LABEL_TRUE
    		assem.OPER jmpLabelTrue = new assem.OPER("jmp `j0", new List<temp.Label>(labelTrue));
    		instructions.append(jmpLabelTrue);
    	}
    }
    
    
    /*
     * Metodo que processa um no do tipo EXP.
     * Verificamos qual a classe do no (TEMP, CONST, etc), e tratamos caso a caso.
     * Feito isso, retornamos um registrador com o conteudo processado, quando necessario. 
     */
    public temp.Temp processExp(List<Instr> instructions, Exp node){
    	temp.Temp register = new temp.Temp();
    	
    	/*
    	 *  Caso onde a exp seja um TEMP.
    	 *  Apenas retornamos o registrador diretamente pelo metodo getTemp().
    	 */
    	if(node.getClass().equals(tree.TEMP.class)){
    		tree.TEMP regtemp = (tree.TEMP)node;
    		return regtemp.getTemp();
    	}
    	/*
    	 *  Caso onde a exp seja uma CONST.
    	 *  Movemos o valor da constante para um registrador temporario, e retornamos este registrador.
    	 */
    	if(node.getClass().equals(tree.CONST.class)){ 
    		tree.CONST constante = (tree.CONST)node;
    		temp.Temp regconst = new temp.Temp();
    		assem.OPER defconst = new assem.OPER("mov `d0, " + constante.getValue(),new List<temp.Temp>(regconst), null);
    		instructions.append(defconst);
    		return regconst;
    	}
    	/*
    	 *  Caso onde a exp seja um ESEQ.
    	 *  Chamamos processStm para a statement, e processExp para a expression (nesta order, de acordo com a especificação).
    	 */
    	if(node.getClass().equals(tree.ESEQ.class)){ 
    		tree.ESEQ eseqTree = (tree.ESEQ)node;
    		processStm(instructions, eseqTree.getStatement());
    		temp.Temp regeseq = processExp(instructions, eseqTree.getExpression());
    		return regeseq;
    	}
    	/*
    	 *  Caso onde a exp seja um MEM.
    	 *  Calculamos a expressão abaixo do nó e realizamos uma operação de 'load' para um registrador temporário.
    	 */
    	if(node.getClass().equals(tree.MEM.class)){ 
    		tree.MEM memTree = (tree.MEM)node;
    		temp.Temp regmem = new temp.Temp();
    		assem.OPER loadInstr = new assem.OPER("mov `d0, [`u0]", new List<temp.Temp>(regmem), new List<temp.Temp>(processExp(instructions, memTree.getExpression())));
    		instructions.append(loadInstr);
    		return regmem;
    	}
    	/*
    	 *  Caso onde a exp seja um BINOP.
    	 *  Calculamos as expressoes da esquerda e da direita e realizamos a operacao de acordo com o inteiro 'op'.
    	 *  - dependendo da operacao, imprime
    	 *  ADD  adicao						PLUS    (0)
			SUB  subtracao					MINUS   (1)
			IMUL multiplicacao				TIMES   (2)
			IDIV divisao					DIV     (3)
			AND  e logico					AND     (4)
			OR   ou logico					OR      (5)
			SHL  shift para a esquerda		LSHIFT  (6)
			SHR  shift para a direita		RSHIFT  (7)
			SAR  shift para a dir.(sin.)	ARSHIFT (8)
			XOR  ou exclusivo logico		XOR     (9)
    	 */
    	if(node.getClass().equals(tree.BINOP.class)){ 
    		// Captura todos os atributos do binop (tipo de operacao e operadores)
    		int operation;
    		temp.Temp regtmp = new temp.Temp();
    		tree.BINOP binopTree = (tree.BINOP)node;
    		operation = binopTree.getOperation();
    		Exp leftOperator = binopTree.getLeft();
    		Exp rightOperator = binopTree.getRight();
    		assem.OPER preBinopInstr;
    		assem.OPER cdqInstr;
    		assem.OPER binopInstr;
    		assem.OPER mulDivInstr;
    		
    		switch(operation){
    		case 0:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			binopInstr = new assem.OPER("add `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
    			break;
    		case 1:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			binopInstr = new assem.OPER("sub `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
    			break;
    		case 2:
    			// utilizamos a operacao 'imul' para realizar o calculo. usamos 'eax' e definimos 'eax' e 'edx'
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(eax), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			binopInstr = new assem.OPER("imul `u0", new List<temp.Temp>(eax, edx), new List<temp.Temp>(processExp(instructions, rightOperator), eax));
    			mulDivInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(eax));
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
        		instructions.append(mulDivInstr);
    			break;
    		case 3:
    			// utilizamos a operacao 'idiv' para realizar o calculo. usamos 'eax' e 'edx' e definimos 'eax'
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			cdqInstr = new assem.OPER("cdq", new List<temp.Temp>(eax, edx), new List<temp.Temp>(eax));
    			binopInstr = new assem.OPER("idiv `u0", new List<temp.Temp>(eax, edx), new List<temp.Temp>(processExp(instructions, rightOperator), eax, edx));
    			mulDivInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(eax));
    			instructions.append(preBinopInstr);
    			instructions.append(cdqInstr);
        		instructions.append(binopInstr);
        		instructions.append(mulDivInstr);
    			break;
    		case 4:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			binopInstr = new assem.OPER("and `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
    			break;
    		case 5:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			binopInstr = new assem.OPER("or `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
    			break;
    		case 6:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			if(rightOperator.getClass().equals(tree.CONST.class)){
    				binopInstr = new assem.OPER("shl `d0, " + ((tree.CONST)rightOperator).getValue(), new List<temp.Temp>(regtmp), null);
    			} else{
    				binopInstr = new assem.OPER("shl `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			}
    			instructions.append(preBinopInstr);
    			instructions.append(binopInstr);	
    			break;
    		case 7:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			if(rightOperator.getClass().equals(tree.CONST.class)){
    				binopInstr = new assem.OPER("shr `d0, " + ((tree.CONST)rightOperator).getValue(), new List<temp.Temp>(regtmp), null);
    			} else {
    				binopInstr = new assem.OPER("shr `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			}
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
    			break;
    		case 8:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			binopInstr = new assem.OPER("sar `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
    			break;
    		case 9:
    			preBinopInstr = new assem.OPER("mov `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, leftOperator)));
    			binopInstr = new assem.OPER("xor `d0, `u0", new List<temp.Temp>(regtmp), new List<temp.Temp>(processExp(instructions, rightOperator)));
    			instructions.append(preBinopInstr);
        		instructions.append(binopInstr);
    			break;
    		default:
    			break;
    		}
    		return regtmp;
    	}
    	/*
    	 *  Caso onde a exp seja um CALL.
    	 */
    	if(node.getClass().equals(tree.CALL.class)){
    		tree.CALL chamada = (tree.CALL)node;
    		
    		//Iterator<Exp> iter = chamada.getArguments().iterator();
    		
    		//Push parameters before the CALL
    		List <Exp> arguments = chamada.getArguments();
    		List<temp.Temp> params = new List<temp.Temp>();
    		
    		for(int i=arguments.size()-1; i>=0; i--){
        		Exp rootNode = arguments.get(i);
        		Temp param = (processExp(instructions, rootNode));
        		params.append(param);
        		assem.OPER pushparam = (new OPER("push `u0", null, new List<temp.Temp>(param)));
        		instructions.append(pushparam);
    		}
    		
    		//Make the CALL
    		if(chamada.getCallable().getClass().equals(tree.NAME.class)){
    			tree.NAME exptemp = (tree.NAME)chamada.getCallable();
    			temp.Label name = exptemp.getLabel();
    			assem.OPER call = new assem.OPER("call " + name, cds, null);
    			instructions.append(call);
    		} else {
    		//if(chamada.getCallable().getClass().equals(tree.MEM.class)){
    			temp.Temp temp = processExp(instructions,chamada.getCallable());
    			assem.OPER call2 = new assem.OPER("call `u0", cds, new List<temp.Temp>(temp));
    			instructions.append(call2);
    		}
    		
    		//cleans up the stack
    		for(int i=arguments.size()-1; i>=0; i--){
    			/*Exp rootNode = (Exp) iter.next();
    			Temp param = (processExp(instructions, rootNode));*/
    			assem.OPER pushparam = (new OPER("pop `u0", null, new List<temp.Temp>(params.get(i))));
    			instructions.append(pushparam);
    		}
    		
    		
//    		int nargs = chamada.getArguments().size();
//    		assem.OPER clean = new assem.OPER("add `d0, " + nargs * wordsize, new List<temp.Temp>(esp), new List<temp.Temp>(esp));
//    		instructions.append(clean);
//    		
    		//return value
    		temp.Temp returnReg = new temp.Temp();
    		assem.MOVE returnValue = new assem.MOVE(returnReg, RV);
    		instructions.append(returnValue);
    		return returnReg;
    		
    	}
    	
    	return register;
    }
    
    
}
